package com.xsb.butterknife.complier;

import com.google.auto.service.AutoService;
import com.xsb.butterknife.annotation.BindView;
import com.xsb.butterknife.annotation.OnClick;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器是Google开发的，
// javax.annotation.processing.Processor
@AutoService(Processor.class)
public class ComplierProcess extends AbstractProcessor {


    private Elements elementUtils;
    private Messager messager;
    private Filer filer;
    private String activityName;

    // 该方法主要用于一些初始化的操作，通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        // 添加支持BindView注解的类型
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        // 返回此注释 Processor 支持的最新的源版本，该方法可以通过注解@SupportedSourceVersion指定
        return super.getSupportedSourceVersion();
    }

    //注解处理器的核心方法，处理具体的注解，生成java文件
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE, "start------------------------------->");
        //获取MainActivity所有带@BindView注解的属性
        Set<? extends Element> bindViewSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //保存键值对，key是com.xsb.butterknife.MainActivity, value是MainActivity带有bindView注解的属性集合
        Map<String, List<VariableElement>> bindViewMap = new HashMap<>();
        //遍历所有带bindview的属性
        for (Element element : bindViewSet) {
            //转化为原始属性元素（结构体元素）
            VariableElement variableElement = (VariableElement) element;
            //通过属性元素获取它所属的MainActivity类名，如：com.xsb.butterknife.MainActivity
            activityName = getActivityName(variableElement);
            //从集合中获取MainActivity所有带BindView注解的集合
            List<VariableElement> list = bindViewMap.get(activityName);
            if (list == null) {
                list = new ArrayList<>();
                //加入map中
                bindViewMap.put(activityName, list);
            }
            //将MainActivity中所有带BindView注解的属性添加到集合中
            list.add(variableElement);
            // 测试打印：每个属性的名字
            messager.printMessage(Diagnostic.Kind.NOTE,
                    "variableElement >>> " + variableElement.getSimpleName().toString());
        }
        //获取MainActivity所有带OnClick注解的方法
        Set<? extends Element> onClickSet = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        //保存键值对，key是com.xsb.butterknife.MainActivity, value是MainActivity带有OnClick注解的属性集合
        Map<String, List<ExecutableElement>> onClickMap = new HashMap<>();
        //遍历OnClick注解的方法
        for (Element element : onClickSet) {
            //转化为原始属性元素（结构体元素）
            ExecutableElement executableElement = (ExecutableElement) element;
            // 通过属性元素获取它所属的MainActivity类名，如：com.xsb.butterknife.MainActivity
            activityName = getActivityName(executableElement);
            //从集合中获取MainActivity所有带OnClick注解的集合
            List<ExecutableElement> list = onClickMap.get(activityName);
            if (list == null) {
                list = new ArrayList<>();
                //加入map中
                onClickMap.put(activityName, list);
            }
            //将MainActivity中所有带onClick注解的属性添加到集合中
            list.add(executableElement);
            // 测试打印：每个方法的名字
            messager.printMessage(Diagnostic.Kind.NOTE,
                    "executableElement >>> " + executableElement.getSimpleName().toString());
        }

        //-------------------------------造币过程--------------------------------------
        // 获取"com.netease.butterknife.MainActivity"中所有控件属性的集合
        List<VariableElement> cacheElements = bindViewMap.get(activityName);
        List<ExecutableElement> clickElements = onClickMap.get(activityName);

        try {
            //创建一个新的源文件（class），并返回一个对象允许写入它
            JavaFileObject javaFileObject = filer.createSourceFile(activityName + "$ViewBinder");
            //通过属性标签获取包名标签（任意一个属性标签的父节点都是同一个包名）
            String packageName = getPackageName(cacheElements.get(0));
            //定义Writer对象，开始造币过程
            Writer writer = javaFileObject.openWriter();
            // 类名：MainActivity$ViewBinder，不是com.netease.butterknife.MainActivity$ViewBinder
            // 通过属性元素获取它所属的MainActivity类名，再拼接后结果为：MainActivity$ViewBinder
            String activitySimpleName = cacheElements.get(0).getEnclosingElement().getSimpleName().toString() + "$ViewBinder";

            messager.printMessage(Diagnostic.Kind.NOTE,
                    "activityName >>> " + activityName + " / activitySimpleName >>> " + activitySimpleName);


            //第一行生成包名
            writer.write("package " + packageName + ";\n");

            //第二行生成要导入的接口类（必须手动导入）
            writer.write("import com.xsb.butterknife.library.ViewBinder" + ";\n");
            writer.write("import com.xsb.butterknife.library.DebouncingOnClickListener" + ";\n");
            writer.write("import android.view.View;\n");

            //第三行生成类
            writer.write("public class " + activitySimpleName + " implements ViewBinder<" + activityName + "> {\n");

            //生成target变量
            writer.write("public " + activityName + " target;\n");

            //生成bind方法
            writer.write("public void bind( final " + activityName + " target){\n");

            //给target变量赋值
            writer.write("this.target = target;\n");

            //循环生成MainActivity的每个控件属性
            for (VariableElement variableElement : cacheElements) {
                String fieldName = variableElement.getSimpleName().toString();
                //获取控件注解
                BindView annotation = variableElement.getAnnotation(BindView.class);
                //获取控件注解值
                int id = annotation.value();
                //生成：target.tv = target.findViewById(xxx);
                writer.write("target." + fieldName + " = target.findViewById(" + id + ");\n");
            }


            //循环生成MainActivity的每个点击事件
            for (ExecutableElement executableElement : clickElements) {
                //获取方法名
                String methodName = executableElement.getSimpleName().toString();
                //获取方法注解
                OnClick annotation = executableElement.getAnnotation(OnClick.class);
                //获取注解值
                int id = annotation.value();
                //获取方法参数
                List<? extends VariableElement> parameters = executableElement.getParameters();
                //生成点击事件
                writer.write("target.findViewById(" + id + ").setOnClickListener(new DebouncingOnClickListener() {\n");
                writer.write("public void doClick(View view){\n");
                if (parameters.isEmpty()) {
                    writer.write("target." + methodName + "();\n}\n});\n");
                } else {
                    writer.write("target." + methodName + "(view);\n}\n});\n");
                }
            }
            //bind方法结束
            writer.write("\n}\n");

            //unBind方法
            writer.write("public void unBind(){\n");
            for (VariableElement cacheElement : cacheElements) {
                String fieldName = cacheElement.getSimpleName().toString();
                writer.write("target." + fieldName +" = null;\n");
            }
            //unbind方法结束
            writer.write("}\n");
            //整个java类文件结束
            writer.write("}\n");
            writer.close();
            messager.printMessage(Diagnostic.Kind.NOTE, "end------------------------------->");
        } catch (Exception e) {
            e.printStackTrace();
        }


        return false;
    }

    private String getActivityName(ExecutableElement executableElement) {
        String packageName = getPackageName(executableElement);
        TypeElement typeElment = (TypeElement) executableElement.getEnclosingElement();
        return packageName + "." + typeElment.getSimpleName().toString();
    }

    private String getPackageName(ExecutableElement executableElement) {
        TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
        String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
        return packageName;
    }

    /**
     * 通过属性标签获取类标签，通过类标签获取包标签
     *
     * @param variableElement 属性标签
     * @return com.xsb.butterknife.MainActivity(包名 + 类名)
     */
    private String getActivityName(VariableElement variableElement) {
        //通过属性标签获取类标签，通过类标签获取包标签
        String packageName = getPackageName(variableElement);
        //通过属性标签获取类名标签
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        //拼接包名和类名
        return packageName + "." + typeElement.getSimpleName().toString();
    }

    // 通过属性标签获取类名标签，再通过类名标签获取包名标签（通过属性节点，找到父节点、再找到父节点的父节点）
    private String getPackageName(VariableElement variableElement) {
        //通过属性标签获取类标签
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        //通过类名标签获取包名标签
        String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
        return packageName;
    }
}
